#ifndef CKKS_SRC_BOOTSTRAPPING_HOMOMORPHIC_MOD_H
#define CKKS_SRC_BOOTSTRAPPING_HOMOMORPHIC_MOD_H
#include <cmath>
#include <complex>
#include <vector>
#include <stdexcept>
#include <cstdint>
#include <functional>
#include "../scheme.h"
#include "../context.h"

// Enum for SineType
enum class SineType {
    Sin = 0,  // Standard Chebyshev approximation of (1/2pi) * sin(2pix)
    Cos1 = 1, // Special approximation (Han and Ki) of pow((1/2pi), 1/2^r) * cos(2pi(x-0.25)/2^r); this method requires
              // a minimum degree of 2*(K-1).
    Cos2 = 2  // Standard Chebyshev approximation of pow((1/2pi), 1/2^r) * cos(2pi(x-0.25)/2^r)
};
// PolynomialBasis is a type for the polynomials basis
enum class PolynomialBasis {
    StandardBasis = 0, // x^(a+b) = x^a * x^b
    ChebyshevBasis = 1 // x^(a+b) = 2 * x^a * x^b - T^|a-b|
};

class EvalModLiteral {
public:
    uint64_t Q;          // Q to reduce by during EvalMod
    int LevelStart;      // Starting level of EvalMod
    long ScalingFactor;  // Scaling factor used during EvalMod
    SineType Sine;       // Chose between [Sin(2*pi*x)] or [cos(2*pi*x/r) with double angle formula]
    double MessageRatio; // Ratio between Q0 and m, i.e. Q[0]/|m|
    int K;               // K parameter (interpolation in the range -K to K)
    int SineDeg;         // Degree of the interpolation
    int DoubleAngle;     // Number of rescale and double angle formula (only applies for cos)
    int ArcSineDeg;      // Degree of the Taylor arcsine composed with f(2*pi*x) (if zero then not used)

    // 构造函数
    EvalModLiteral(uint64_t q, int levelStart, long scalingFactor, SineType SineType, double messageRatio, int k,
        int sineDeg, int doubleAngle, int arcSineDeg);
    // 拷贝构造函数
    EvalModLiteral(const EvalModLiteral &cipher) = default;
    EvalModLiteral &operator = (const EvalModLiteral &o) = default;
    // 移动构造函数
    EvalModLiteral(EvalModLiteral &&other) = delete;
    EvalModLiteral &operator = (EvalModLiteral &&other) = delete;

    double QDiff() const;
    int Depth() const;
};

// Polynomial is a class storing the coefficients of a polynomial
// that then can be evaluated on the ciphertext
class Polynomial {
public:
    int MaxDeg;                               // Maximum degree of the polynomial
    std::vector<std::complex<double>> Coeffs; // Coefficients of the polynomial
    bool Lead;                                // Leading term
    double A;                                 // Parameter A
    double B;                                 // Parameter B
    PolynomialBasis Basis;                    // Basis of the polynomial

    // Constructor to initialize the class
    Polynomial(int maxDeg, const std::vector<std::complex<double>> &coeffs, bool lead, double a, double b,
        PolynomialBasis basis);
    Polynomial(const std::vector<std::complex<double>> &coeffs, bool lead, double a, double b);

    Polynomial();

    // Constructor to initialize with coefficients only
    explicit Polynomial(const std::vector<std::complex<double>> &coeffs);
    // Static method to create a new polynomial from coefficients
    static Polynomial *NewPoly(std::vector<std::complex<double>> &coeffs);

    int Depth() const;
    int Degree() const;

    static std::vector<double> chebyshevNodes(int n, double a, double b);

    static std::vector<std::complex<double>> chebyCoeffs(const std::vector<double> &nodes,
        const std::vector<std::complex<double>> &fi, double a, double b);

    Polynomial *Approximate(const std::function<std::complex<double>(std::complex<double>)> &function, double a,
        double b, int degree);

    static Polynomial *Approximate(std::complex<double> (*func)(const std::complex<double> &), double a, double b,
        int degree);
};


class EvalModPoly {
public:
    int levelStart;
    long scalingFactor;
    SineType sineType;
    double messageRatio;
    int doubleAngle;
    double qDiff;
    double scFac;
    double sqrt2Pi;
    Polynomial *sinePoly;
    Polynomial *arcSinePoly;

    EvalModPoly(int levelStart, long scalingFactor, SineType sineType, double messageRatio, int doubleAngle,
        double qDiff, double scFac, double sqrt2Pi, Polynomial *sinePoly, Polynomial *arcSinePoly)
        : levelStart(levelStart),
          scalingFactor(scalingFactor),
          sineType(sineType),
          messageRatio(messageRatio),
          doubleAngle(doubleAngle),
          qDiff(qDiff),
          scFac(scFac),
          sqrt2Pi(sqrt2Pi),
          sinePoly(sinePoly),
          arcSinePoly(arcSinePoly)
    {}


    EvalModPoly();
    int LevelStart() const;

    double ScalingFactor() const;

    double ScFac() const;

    double MessageRatio() const;

    double A() const;

    double B() const;

    double K() const;

    double QDiff() const;

    static EvalModPoly NewEvalModPolyFromLiteral(EvalModLiteral evm);
};

#endif
